use strict;
use warnings;

package Lingua::EN::WSD::WebBased::Vector;

use base qw/Exporter/;

use Lingua::EN::WSD::WebBased::Logger;
use Lingua::EN::WSD::WebBased::Utils;

use Class::AccessorMaker {
    seen                => { },
    unseen              => 0,
    vocabulary          => { },
    vocabulary_size     => undef
}, 'new_init';

our @EXPORT = qw/
    build_sparse_vector
    build_vocabulary
/;

sub init {

    my $self = shift;

    check_type('vocabulary', $self->vocabulary, 'HASH');
    
    $self->vocabulary_size(scalar keys %{$self->vocabulary})
        if !defined $self->vocabulary_size;

    for (keys %{$self->{seen}}) {
        if (!exists $self->{vocabulary}{$_}) {
            delete $self->{seen}{$_};
            print STDERR "^$_\n";
        }
    }

}

sub UNTIE       { }
sub DESTROY     { }

sub TIEHASH     {

    my ($class, %attrs) = @_;
    
    return new($class, %attrs);

}

sub FETCH {

    my ($self, $key) = @_;

    return
        exists $self->{seen}{$key} ? $self->{seen}{$key} : 
        exists $self->{vocabulary}{$key} ? $self->{unseen} : undef;

}

sub STORE {

    my ($self, $key, $value) = @_;

    $self->{seen}{$key} = $value if exists $self->{vocabulary}{$key};

}

sub DELETE {

    my ($self, $key) = @_;

    delete $self->{seen}{$key} if exists $self->{seen}{$key};

}

sub EXISTS {

    my ($self, $key) = @_;

    return exists $self->vocabulary->{$key};

}

sub FIRSTKEY {

    my ($self, $key) = @_;

    my $a = keys %{$self->vocabulary};
    
    return each %{$self->vocabulary};

}

sub NEXTKEY {

    my ($self, $key) = @_;

    return each %{$self->vocabulary};

}

sub SCALAR {

    my ($self, $key) = @_;

    return scalar %{$self->vocabulary};

}

sub get_unseen_num {

    my $self = shift;

    return $self->vocabulary_size - scalar keys %{$self->seen};

}

sub normalize {
    
    my $self = shift;
    
    my $sum = (sum values %{$self->{seen}}) +
        $self->{unseen} * ($self->get_unseen_num);

    $self->{seen}{$_} /= $sum for keys %{$self->{seen}};
    $self->{unseen} /= $sum;

    return $self;

}


sub smooth {

    my $self = shift;
    
    _warn('vector is already smoothed') if $self->unseen != 0;
    
    $self->unseen(0);
    
    my %freq_classes;
    my $sum = 0;
    while (my ($term, $freq) = each %{$self->{seen}}) {
        $freq_classes{$freq}++;
        $sum += $freq;
    }

    # use Simple Good-Turning when #freq_classes >= 5
    if (keys %freq_classes >= 5) {    
        use Statistics::Smoothing::SGT;
        my $sgt = new Statistics::Smoothing::SGT(\%freq_classes, $sum);
        $sgt->calculateValues;
        my $prob = $sgt->getProbabilities;
        while (my ($term, $freq) = each %{$self->{seen}}) {
            $self->{seen}{$term} = $sum * $prob->{$freq};
        }
        $self->unseen($sum * $prob->{0} / $self->get_unseen_num);
    }
    
    # use add-one when #freq_classes < 5
    else {
        while (my ($term, $freq) = each %{$self->{seen}}) {
            $self->{seen}{$term}++;
        }
        $self->unseen(1);
    }

    return $self;

}

sub flatten {

    my ($self, $sub_vocabulary) = @_;

    return [ map { $self->{seen}{$_} || $self->{unseen} } @$sub_vocabulary ];

}

# build a sparse vector using Lingua::EN::WSD::WebBased::Vector

sub build_sparse_vector {

    my ($vector, $vocabulary, $vocabulary_size) = @_;

    tie my %vector, 'Lingua::EN::WSD::WebBased::Vector',
        seen => $vector, unseen => 0,
        vocabulary => $vocabulary, vocabulary_size => $vocabulary_size;

    return \%vector;

}

# build vocabulary hash from vocabulary array

sub build_vocabulary {

    my $vocabulary = shift;

    my %vocabulary = map { $_ => 1 } @$vocabulary;

    return wantarray ? (\%vocabulary, scalar @$vocabulary) : \%vocabulary;

}

1;

